package com.sensoro.beacon.kit;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 处理beacon服务
 */
public class BeaconProcessService extends Service {
    private static final String TAG = BeaconProcessService.class.getSimpleName();

    private static final int BEACON_EVENT_MSG_NEW = 0;
    private static final int BEACON_EVENT_MSG_GONE = 1;
    private static final int BEACON_MSG_UPDATE = 2;

    public static final int START_MONITOR_REGION = 0;
    public static final int STOP_MONITOR_REGION = 1;

    public static final String MONITORED_REGION = "MONITORED_REGION";
    public static final String UPDATE_BEACONS_IN_REGION = "UPDATE_BEACONS_IN_REGION";
    public static final String MONITORED_BEACON = "MONITORED_BEACON";
    public static final String UPDATE_BEACONS = "UPDATE_BEACONS";

    private volatile long updateBeaconPeriod = SensoroBeaconManager.UPDATE_BEACON_PERIOD; // 定时更新Beacon时间间隔

    private static final long CHECK_BLE_EVENT_TIMEOUT_SPACE = 1000;

    private ConcurrentHashMap<String, BeaconEvent> beaconEventConcurrentHashMap;

    private Handler beaconHandler;
    private Handler updateBeaconsHandler;
    private Handler deamonHandler;
    private UpdateBeaconRunnable updateBeaconRunnable;
    private BeaconEventRunnable beaconEventRunnable;

    private BeaconService beaconService = null;
    private volatile BeaconUpdateListener beaconUpdateListener = null;
    private HashMap<String, byte[]> broadcastKeyMap = new HashMap<String, byte[]>();
    private ArrayList<Beacon> beacons = new ArrayList<Beacon>();
    private ArrayList<Beacon> updateBeacons = new ArrayList<Beacon>();
    private List<BeaconManagerListener> beaconManagerListenerList;
    private Object listenerLock = new Object();
    private volatile long existTime = SensoroBeaconManager.OUT_OF_RANGE_DELAY;
    private int boundCount = 0;

    private ConcurrentHashMap<Beacon, BeaconState> monitoredBeaconState = new ConcurrentHashMap<Beacon, BeaconState>();
    private ConcurrentHashMap<Region, RegionState> monitoredRegionState = new ConcurrentHashMap<Region, RegionState>();

    private Messenger messenger = new Messenger(new IncomingHandler(this));

    private class IncomingHandler extends Handler {
        private final WeakReference<BeaconProcessService> service;

        IncomingHandler(BeaconProcessService service) {
            this.service = new WeakReference<BeaconProcessService>(service);
        }

        @Override
        public void handleMessage(Message msg) {
            BeaconProcessService service = this.service.get();
            Region region = (Region) msg.obj;
            if (service != null) {
                switch (msg.what) {
                    case START_MONITOR_REGION:
                        service.startMonitorRegion(region);
                        break;
                    case STOP_MONITOR_REGION:
                        service.stopMonitorRegion(region);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    }

    private void startMonitorRegion(Region region) {
        synchronized (monitoredRegionState) {
            if (!monitoredRegionState.containsKey(region)) {
                monitoredRegionState.put(region, new RegionState());
            }
        }
    }

    private void stopMonitorRegion(Region region) {
        synchronized (monitoredRegionState) {
            monitoredRegionState.remove(region);
        }
    }


    public class BeaconProcessServiceBinder extends Binder {
        public BeaconProcessService getService() {
            return BeaconProcessService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "onBind");
        }
        boundCount++;

        // 绑定BeaconService
        bindBeaconService();

        // 启动守护进程
        deamonHandler = new Handler();
        deamonHandler.postDelayed(new DaemonRunnable(), CHECK_BLE_EVENT_TIMEOUT_SPACE);

        // 初始化自动更新beacons
        updateBeaconsHandler = new Handler();
        updateBeaconRunnable = new UpdateBeaconRunnable();

        // 绑定服务回调 beacon update
        updateBeaconsHandler.postDelayed(updateBeaconRunnable, updateBeaconPeriod);
        return new BeaconProcessServiceBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if (SensoroBeaconManager.DEBUG) {
            Log.d(TAG, "onUnbind");
        }
        boundCount--;
        unbindService(serviceConnection);

        // 解绑服务停止回调 beacon update
        updateBeaconsHandler.removeCallbacks(updateBeaconRunnable);
        return super.onUnbind(intent);
    }

    public boolean isBound() {
        // 默认有 SensoroBeaconService bind
        if (boundCount == 0) {
            return false;
        } else {
            return true;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void initBeaconEventThread() {

        if (beaconEventRunnable == null) {
            beaconEventRunnable = new BeaconEventRunnable();
            Thread beaconEventThread = new Thread(beaconEventRunnable);
            beaconEventThread.start();
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        beaconManagerListenerList = new ArrayList<BeaconManagerListener>();

        // 处理BeaconEvent事件
        initBeaconEventThread();

        // 初始化BeaconEvent容器
        initContainer();
    }

    private void bindBeaconService() {
        Intent intent = new Intent();
        intent.setClass(BeaconProcessService.this, BeaconService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            BeaconService.BeaconBinder beaconBinder = (BeaconService.BeaconBinder) iBinder;
            beaconService = beaconBinder.getService();
            beaconUpdateListener = new BeaconUpdateListener() {
                @SuppressWarnings("unchecked")
                @Override
                public void didBeacons(ArrayList<Beacon> beacons) {
                    // Log.v(TAG, "didBeacons beacons size = " +
                    // beacons.size());
                    // Callback list of deepcopy, to resolve
                    // ConcurrentModificationException bug
//                    updateBeacons = (List<Beacon>) beacons.clone();
                    new BeaconsProcessor().doInBackground(beacons);
                }
            };
            beaconService.setBeaconUpdateListener(beaconUpdateListener);
            beaconService.setBroadcastKeyMap(broadcastKeyMap);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    private class BeaconsProcessor extends AsyncTask<ArrayList<Beacon>, Void, Void> {

        @Override
        protected Void doInBackground(ArrayList<Beacon>... params) {
            beacons = params[0];
//            // Log.e(TAG,"backgroud process beacons size" + beacons.size());
//            for (Beacon beacon : beacons) {
//                // 将HW的Beacon转换成BeaconEvent
//                BeaconEvent beaconEvent = getBeaconEvent(beacon, new Date().getTime());
//                // 如果没有对应(key,value)则加入,并发送发现新beacon的广播;如果有则更新
//                String key = beaconEvent.beacon.getSerialNumber();
//                if (!beaconEventConcurrentHashMap.containsKey(key)) {
//                    // Log.v(TAG, "send onNew broadcast---major:" +
//                    // beaconEvent.beacon.getMajor() + "minor:" +
//                    // beaconEvent.beacon.getMinor());
//
//                    Message msg = beaconHandler.obtainMessage(BEACON_EVENT_MSG_NEW, beacon);
//                    beaconHandler.sendMessage(msg);
//                }
//                beaconEventConcurrentHashMap.put(beaconEvent.beacon.getSerialNumber(), beaconEvent);
//            }

            processBeacons(beacons);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
        }

        @Override
        protected void onPreExecute() {
        }

        @Override
        protected void onProgressUpdate(Void... values) {
        }
    }

    private void processBeacons(ArrayList<Beacon> beacons) {
        updateBeaconList(beacons);
        enterBeaconOrRegion(beacons);
        exitBeaconOrRegion(beacons);
    }

    private void exitBeaconOrRegion(ArrayList<Beacon> beacons) {
        // 遍历所有保存的 beacon，查看是否有过期
        Iterator beaconIterator = monitoredBeaconState.entrySet().iterator();
        while (beaconIterator.hasNext()) {
            Map.Entry entry = (Map.Entry) beaconIterator.next();
            Beacon monitoredBeacon = (Beacon) entry.getKey();
            BeaconState beaconState = (BeaconState) entry.getValue();
            if (System.currentTimeMillis() - beaconState.lastFoundTime > SensoroBeaconManager.OUT_OF_RANGE_DELAY) {
                // gone beacon
                Beacon goneBeacon = null;
                try {
                    goneBeacon = monitoredBeacon.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                Intent intent = new Intent();
                intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
                intent.putExtra(MONITORED_BEACON, new MonitoredBeacon(goneBeacon, false));
                startService(intent);

                processBeaconOutRegion(monitoredBeacon);
                monitoredBeaconState.remove(monitoredBeacon);
            }
        }
    }

    private void enterBeaconOrRegion(ArrayList<Beacon> beacons) {
        for (Beacon beacon : beacons) {
            // beacon is not monitored
            if (!monitoredBeaconState.containsKey(beacon)) {
                // new beacon
                Beacon newBeacon = null;
                try {
                    newBeacon = beacon.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                Intent intent = new Intent();
                intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
                intent.putExtra(MONITORED_BEACON, new MonitoredBeacon(newBeacon, true));
                startService(intent);

                processBeaconInRegion(beacon);
                monitoredBeaconState.put(beacon, new BeaconState(System.currentTimeMillis()));
            } else {
                BeaconState beaconState = monitoredBeaconState.get(beacon);
                beaconState.lastFoundTime = System.currentTimeMillis();
            }
        }
    }

    private void updateBeaconList(ArrayList<Beacon> beacons) {
        updateBeacons = (ArrayList<Beacon>) beacons.clone();
        Intent intent = new Intent();
        intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
        intent.putParcelableArrayListExtra(UPDATE_BEACONS, updateBeacons);
        startService(intent);
    }

    private void processBeaconInRegion(Beacon beacon) {
        Iterator regionIterator = monitoredRegionState.entrySet().iterator();
        while (regionIterator.hasNext()) {
            Map.Entry entry = (Map.Entry) regionIterator.next();
            Region region = (Region) entry.getKey();
            RegionState regionState = (RegionState) entry.getValue();

            // beacon belongs to region
            if (isBeaconInRegion(region, beacon)) {
                // region is outside
                if (!regionState.inSide) {
                    region.beacons.add(beacon);
                    // enter region
                    Region enterRegion = null;
                    try {
                        enterRegion = region.clone();
                    } catch (CloneNotSupportedException e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent();
                    intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
                    intent.putExtra(MONITORED_REGION, new MonitoredRegion(enterRegion, false));
                    startService(intent);
                    Log.e("Region", "on new region");

                    regionState.inSide = true;
                } else {
                    // region is inside, update beacons in region
                    region.beacons.add(beacon);
                    // update region
                    Region updateRegion = null;
                    try {
                        updateRegion = region.clone();
                    } catch (CloneNotSupportedException e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent();
                    intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
                    intent.putExtra(UPDATE_BEACONS_IN_REGION, new MonitoredRegion(updateRegion, false));
                    startService(intent);
                    Log.e("Region", "update beacons in region");
                }
            }
        }
    }

    private void processBeaconOutRegion(Beacon beacon) {
        synchronized (monitoredRegionState) {
            Iterator regionIterator = monitoredRegionState.entrySet().iterator();
            while (regionIterator.hasNext()) {
                Map.Entry entry = (Map.Entry) regionIterator.next();
                Region region = (Region) entry.getKey();
                RegionState regionState = (RegionState) entry.getValue();

                // beacon belongs to region
                if (isBeaconInRegion(region, beacon)) {
                    // in region
                    if (regionState.inSide) {
                        region.beacons.remove(beacon);
                        if (region.beacons.size() == 0) {
                            regionState.inSide = false;
                            // exit region
                            Region exitRegion = null;
                            try {
                                exitRegion = region.clone();
                            } catch (CloneNotSupportedException e) {
                                e.printStackTrace();
                            }

                            Intent intent = new Intent();
                            intent.setClass(BeaconProcessService.this, IntentProcessorService.class);
                            intent.putExtra(MONITORED_REGION, new MonitoredRegion(exitRegion, false));
                            startService(intent);
                            Log.e("AAAAA", "on gone region");
                        }
                    }
                }
            }
        }
    }

    private boolean isBeaconInRegion(Region region, Beacon beacon) {
        switch (region.type) {
            case Region.TYPE_U:
                if (matchUUID(region.proximityUUID, beacon.proximityUUID)) {
                    return true;
                }
            case Region.TYPE_UM:
                if (matchUUID(region.proximityUUID, beacon.proximityUUID) && region.major != Integer.MAX_VALUE && region.major == beacon.major) {
                    return true;
                }
                break;
            case Region.TYPE_UMM:
                if (matchUUID(region.proximityUUID, beacon.proximityUUID) && region.major != Integer.MAX_VALUE && region.major == beacon.major && region.minor != Integer.MAX_VALUE && region.minor == beacon.minor) {
                    return true;
                }
                break;
            case Region.TYPE_UNKNOWN:
                return false;
            default:
                return false;
        }
        return false;
    }

    private boolean matchUUID(String regionUUID, String beaconUUID) {
        if (!regionUUID.equals("") && beaconUUID != null && regionUUID.equals(beaconUUID)) {
            return true;
        } else {
            return false;
        }
    }

    private BeaconEvent getBeaconEvent(Beacon beacon, long time) {
        return new BeaconEvent(beacon, time);
    }

    private void initContainer() {
        if (beaconEventConcurrentHashMap == null) {
            beaconEventConcurrentHashMap = new ConcurrentHashMap<String, BeaconEvent>();
        }
    }

    /**
     * 检测beacon是否超时的线程,如果发现beacon超时则将该beacon从map中删除
     */
    class DaemonRunnable implements Runnable {

        @Override
        public void run() {
            // 每隔1秒取判断bleEvent容器是否有过期
            long time = new Date().getTime();
            Set<String> keySet = beaconEventConcurrentHashMap.keySet();
            for (String key : keySet) {
                BeaconEvent beaconEvent = beaconEventConcurrentHashMap.get(key);
                if (beaconEvent == null) {
                    continue;
                }
                // long currentTime = System.nanoTime() / 1000000;
                // long space = currentTime - bleEvent.time;
                long space = time - beaconEvent.time;
                if (space > existTime) {
                    // Log.v(TAG, "send onGone broadcast---major:" +
                    // beaconEvent.beacon.getMajor() + "minor:" +
                    // beaconEvent.beacon.getMinor());
                    // 发送beacon消失的广播
                    Message msg = beaconHandler.obtainMessage(BEACON_EVENT_MSG_GONE, beaconEvent.beacon);
                    beaconHandler.sendMessage(msg);
                    beaconEventConcurrentHashMap.remove(key);
                }
            }
            deamonHandler.postDelayed(new DaemonRunnable(), CHECK_BLE_EVENT_TIMEOUT_SPACE);
        }
    }

    class UpdateBeaconRunnable implements Runnable {

        @Override
        public void run() {
            // 每秒更新一次当前能搜索到的beacon信息
            List<Beacon> beacons = getBeacons();
            Message msg = beaconHandler.obtainMessage(BEACON_MSG_UPDATE, beacons);
            beaconHandler.sendMessage(msg);
            updateBeaconsHandler.postDelayed(updateBeaconRunnable, updateBeaconPeriod);
        }
    }

    private List<Beacon> getBeacons() {
//        if (System.currentTimeMillis() - updateTime > updateBeaconPeriod) {
//            return new ArrayList<Beacon>();
//        }
        return updateBeacons;
    }

    /**
     * 处理BeaconEvent的线程
     */
    class BeaconEventRunnable implements Runnable {

        @Override
        public void run() {
            Looper.prepare();
            beaconHandler = new Handler() {
                @SuppressWarnings("unchecked")
                @Override
                public void handleMessage(Message msg) {
                    int what = msg.what;
                    Beacon beacon;
                    switch (what) {
                        case BEACON_EVENT_MSG_NEW:
                            // 发现新的beacon
                            beacon = (Beacon) msg.obj;
                            notifyAllOnNewBeacon(beacon);
                            break;
                        case BEACON_EVENT_MSG_GONE:
                            // beacon消失
                            beacon = (Beacon) msg.obj;
                            notifyAllOnGoneBeacon(beacon);
                            break;
                        case BEACON_MSG_UPDATE:
                            // 每秒更新Beacon信息
                            List<Beacon> beacons = (List<Beacon>) msg.obj;
                            notifyAllOnUpdateBeacon(beacons);
                            break;
                        default:
                            break;
                    }
                    super.handleMessage(msg);
                }
            };
            Looper.loop();
        }
    }

    /**
     * 通知所有监听发现新的 beacon
     *
     * @param beacon
     */
    private void notifyAllOnNewBeacon(Beacon beacon) {
        synchronized (listenerLock) {
            for (BeaconManagerListener beaconManagerListener : beaconManagerListenerList) {
                beaconManagerListener.onNewBeacon(beacon);
            }
        }
    }

    /**
     * 通知所有监听 beacon 消失
     *
     * @param beacon
     */
    private void notifyAllOnGoneBeacon(Beacon beacon) {
        synchronized (listenerLock) {
            for (BeaconManagerListener beaconManagerListener : beaconManagerListenerList) {
                beaconManagerListener.onGoneBeacon(beacon);
            }
        }
    }

    /**
     * 通知所有监听 beacon 列表更新
     *
     * @param beacons
     */
    private void notifyAllOnUpdateBeacon(List<Beacon> beacons) {
        synchronized (listenerLock) {
            for (BeaconManagerListener beaconManagerListener : beaconManagerListenerList) {
                ArrayList<Beacon> beaconArrayList = new ArrayList<Beacon>(beacons);
                beaconManagerListener.onUpdateBeacon(beaconArrayList);
            }
        }
    }

    public void setUpdateBeaconPeriod(long period) {
        updateBeaconPeriod = period;
    }

    public void registerListener(BeaconManagerListener beaconManagerListener) {
        synchronized (listenerLock) {
            beaconManagerListenerList.clear();
            beaconManagerListenerList.add(beaconManagerListener);
        }
    }

    public void unregisterListener(BeaconManagerListener beaconManagerListener) {
        synchronized (listenerLock) {
            beaconManagerListenerList.remove(beaconManagerListener);
        }
    }

    public void setBeaconExitTime(long exitTime) {
        this.existTime = exitTime;
    }

    public void setBackgroundMode(boolean backgroundMode) {
        if (beaconService != null) {
            beaconService.setBackgroundMode(backgroundMode);
        }
    }

    public void setBroadcastKeyMap(HashMap<String, byte[]> broadcastKeyMap) {
        this.broadcastKeyMap = broadcastKeyMap;
        if (beaconService != null) {
            beaconService.setBroadcastKeyMap(broadcastKeyMap);
        }
    }
}
